Skip to content

Add build constraints #13534

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

notatallshaw
Copy link
Member

@notatallshaw notatallshaw commented Aug 9, 2025

Fixes #13300

This adds a new --build-constraint flag to complement the existing --constraint flag, at a high level:

  1. Build constraints are used as constraints for build requirements in isolated build environments
  2. Regular constraints are no longer used as constraints in any isolated build environments (normally done via PIP_CONSTRAINT now requires PIP_BUILD_CONSTRAINT or simply --build-constraint)
  3. Because this changes existing behavior the new behavior is only enabled when build constraints are passed or the --use-feature flag build-constraint is enabled, as not to break user workflows
  4. There is a deprecation warning when using PIP_CONSTRAINT is present and in effect without using the new build constraints

This change in behavior was desired because:

  1. It is a simpler UX than the existing behavior of specifying both regular and build time constraints (CLI flags for run time and ENV vars for isolated build vs. --constraint and --build-constraint)
  2. This behavior will be easier to match with in-process installation (Install build dependencies in-process #13450) where as the existing behavior is trickier
  3. It is the same behavior uv pip has for --constraint and --build-constraint so is well verified in the real world

…needing to use the `build-constraint` feature
@notatallshaw notatallshaw force-pushed the add-build-constraints branch from 6c44d43 to 8d170b5 Compare August 9, 2025 14:09
@potiuk
Copy link
Contributor

potiuk commented Aug 11, 2025

Very, very much needed. Thanks @notatallshaw -> today setuptools-scm 9.1.0 (2 hours ago) again broke source installation of xmlsec: pypa/setuptools-scm#1194

@potiuk
Copy link
Contributor

potiuk commented Aug 11, 2025

Very, very much needed. Thanks @notatallshaw -> today setuptools-scm 9.1.0 (2 hours ago) again broke source installation of xmlsec: pypa/setuptools-scm#1194

Yanked now luckily.

Looking forward to this one - I really like how this one will help us in the future to make both - our PRs and installation mechanisms we tell our users, to make them resilient to similar accidents broken packages released on PyPI. I really look forward to this one to make it part of our regular process. The existing workarounds are complicated and non-end-user friendly at all and would complicate our CI process quite a lot as we woudl have to combine regular and build constraints dynamically on our CI.

@notatallshaw
Copy link
Member Author

notatallshaw commented Aug 11, 2025

Thanks @potiuk, a few things worth nothing though:

This only allows users to protect themselves from breakages, not libraries. I feel like there is an open question about whether libraries (or popular applications) should provide upperbounds on their build dependencies. In fact it was recently discussed in pypa/packaging.python.org#1880 (comment)

This doesn't technically introduce any new functionality if you have full control of pip you can reproduce all of this:

  • Install only constraints: --constraint
  • Install and build time constraints single file: PIP_CONSTRAINT
  • Install and build time constraints separate files: --constraint and PIP_CONSTRAINT
  • Build time only constraints: --constraint pointed to an empty file and PIP_CONSTRAINT

But this is poor UX, it means you don't have the usual CLI and ENV flexibility, and the fact this works the way it does depends on multiple internal details of how pip is implemented. In fact what finally inspired me to write this PR is that one of those internal details (installing build dependencies via calling pip in a subprocess) is likely to change soon.

@potiuk
Copy link
Contributor

potiuk commented Aug 11, 2025

Yep. I agree 100% with everythi g you wrote. I already started lookng at implementing a co bo of co ateaints solution in our CI this morning - bit I was so relieved I did not have to (yet) as today's aetuptools-scm 9.1.0 was yanked - precisely because of the poor UX and the undocumented, internal behaviour and potential conflicts between build and regular constraints which has kinda unspecified behaviour.

And - as for Airflow - we simply pin our build dependencies - because in our case reproducibility trumps potential security issues with build dependencies - and also it makes it easier for us because we are pure Python only so very few upstream users would actually use .sdist to build airflow - and if they do, they usually do it in controlled environments.

Copy link
Member

@ichard26 ichard26 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks quite good already! Thanks a lot for thinking through the scenarios and interactions thoroughly. I'm impressed with your unit and functional test.

This is my first review pass which mostly consists of minor comments. I'll want to do another pass before I approve.

A general note is that the codebase seems to have standardized around constraints for --constraint while this PR uses build_constraint for --build-constraints. I'd prefer that we use build_constraints with a S internally for consistency, but I do acknowledge that constraints is not consistent with the CLI.

Comment on lines +134 to +143
if not self._constraints:
return

if not os.environ.get("PIP_CONSTRAINT"):
return

pip_constraint_files = [
f for f in os.environ["PIP_CONSTRAINT"].split() if f.strip()
]
if pip_constraint_files and pip_constraint_files == self._constraints:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume the scenario where the stored constraints do not match PIP_CONSTRAINT mostly involve configuration files?

@@ -112,6 +164,8 @@ def install(
kind: str,
for_req: InstallRequirement | None,
) -> None:
self._deprecation_constraint_check()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably issue this deprecation warning only once. In some scenarios, even a single build will trigger this deprecation twice as the build backend can request additional dependencies dynamically.

... although I say that having tried this locally... I can't get the deprecation to be printed twice. Hmm.

Comment on lines +35 to +36
class ExtraEnviron(TypedDict, total=False):
extra_environ: dict[str, str]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the benefit of defining this typed dictionary? It seems superfluous IMO.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am all open to suggestions that don't make the code more awkward (e.g writing a much larger if block) and keep mypy happy.

I could move it into the if type checking block though to eliminate any run time construction.

Comment on lines +104 to +105
def check_build_constraints(options: Values) -> None:
"""Function for validating build constraint options.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good forward thinking here!

Comment on lines +56 to +58
def _assert_successful_installation(result: TestPipResult, package_name: str) -> None:
"""Assert that the package was successfully installed."""
assert f"Successfully installed {package_name}" in result.stdout
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't there an result.assert_installed() helper for this? It does require a version though.

Comment on lines +145 to +147
def test_build_constraints_file_not_found(
script: PipTestEnvironment, tmpdir: Path
) -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason why --build-constraint will fail silently if the file isn't found? This deviates from --constraint which will loudly fail if the file can't be read.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to include a separate deprecation news entry for PIP_CONSTRAINT? Deprecation news entries are placed at the very top of the changelog section which would be good IMO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature: Specify constraints file for the isolated build environment
4 participants